using System;
using System.Collections.Generic;
using System.IO;
using mp;
using mp.Models;

namespace mp.Analyzer
{
	public class Analyzer
	{
		public Analyzer(string fileName)
		{
			if(File.Exists(fileName))
				File.Delete(fileName);			
			writer = new StreamWriter(fileName);						
		}
		
		public void gen_AddOp(Semantic_rec addOp_rec)
		{
			if(addOp_rec.AddingOp == AddingOp.Plus.GetHashCode())
				writer.WriteLine("ADDS");
			else if(addOp_rec.AddingOp == AddingOp.Minus.GetHashCode())
				writer.WriteLine("SUBS");
			else
			{
				var firstLabel = newLabel();
				var secondLabel = newLabel();
				writer.WriteLine("BNE -2(SP) #0 {0}", firstLabel);
				writer.WriteLine("BNE -1(SP) #0 {0}", firstLabel);
				this.gen_SubStack(2);
				this.gen_PushNum("0");
				this.gen_Branch(secondLabel);
				this.gen_Label(firstLabel);
				this.gen_SubStack(2);
				this.gen_PushNum("1");
				this.gen_Label(secondLabel);
			}
		}
		
		public void gen_Assignment(SymbolTable table, string iden_rec, Semantic_rec expression_rec)
		{
			var symbol = table.GetSymbol(iden_rec);
			if(symbol != null)
			{
				if(expression_rec.Type == symbol.Type)
					this.gen_PopVar(symbol);
				else
					Console.WriteLine("The type of variable {0} and the expression do not match in assignment.", iden_rec);
			}
			else
				Console.WriteLine("Variable {0} has not been defined.", iden_rec);
		}
		
		public void gen_BlockEnd(int variables)
		{
			this.gen_SubStack(variables);
		}
		
		public void gen_Branch(string label)
		{
			writer.WriteLine("BR {0}", label);
		}
		
		public void gen_ElsePart(string finishLabel, string elseLabel)
		{
			this.gen_Branch(finishLabel);				
			this.gen_Label(elseLabel);
			this.gen_SubStack(1);
		}
		
		public void gen_ForStatementCheck(ref string endLabel, StepValue_rec stepValue_rec, string topOfLoop, Symbol controlSymbol)
		{
			this.gen_Label(topOfLoop);
			this.gen_PushVar(controlSymbol);
			
			endLabel = newLabel();
			if(stepValue_rec.downto)
			{
				writer.WriteLine("BGT -2(SP) -1(SP) {0}", endLabel);
			}
			else
			{
				writer.WriteLine("BLT -2(SP) -1(SP) {0}", endLabel);
			}
			this.gen_SubStack(1);
		}
		
		public void gen_ForStatementEnd(string topOfLoop, string endLabel, StepValue_rec stepValue_rec, Symbol controlSymbol)
		{
			this.gen_PushVar(controlSymbol);
			this.gen_PushNum("1");
			if(stepValue_rec.downto)			
				writer.WriteLine("SUBS");
			else
				writer.WriteLine("ADDS");
			this.gen_PopVar(controlSymbol);
			this.gen_Branch(topOfLoop);
			this.gen_Label(endLabel);
			this.gen_SubStack(2);
		}
		
		public void gen_ForStatementStart(SymbolTable table, string controlVariable, ref string topOfLoop, ref Symbol controlSymbol)
		{
			topOfLoop = newLabel();
			controlSymbol = table.GetSymbol(controlVariable);
			if(controlSymbol != null)
			{
				this.gen_PopVar(controlSymbol);
			}
			else
				Console.WriteLine("The variable {0} does not exist.", controlVariable);
		}
		
		public void gen_FunctionAssignment(SymbolTable table, string iden_rec, Semantic_rec expression_rec)
		{
			var symbol = table.GetSymbol(iden_rec);
			if(symbol != null)
			{
				if(expression_rec.Type == symbol.Type)
					if(table.Level > 0)
						writer.WriteLine("POP -2(D{0})", table.Level);
					else 
						Console.WriteLine("Can not assign to function if you're not in a function");
				else
					Console.WriteLine("The type of variable {0} and the expression do not match in assignment.", iden_rec);
			}
			else
				Console.WriteLine("Function {0} has not been defined.", iden_rec);
		}
		
		public void gen_FunctionCallAndCleanUp(Symbol function, ProcedureOrFunction_rec function_rec, int level)
		{
			if(function != null)
			{
				writer.WriteLine("CALL {0}", function.Label);
				this.gen_SubStack(function.Params.Count);
				writer.WriteLine("POP D{0}", level);
			}
			else
				Console.WriteLine("The procedure {0} has not been defined.", function_rec.identifier);
		}
		
		public void gen_IfCheck(ref string elseLabel, ref string finalLabel)
		{
			elseLabel = newLabel();
			finalLabel = newLabel();
			writer.WriteLine("BEQ -1(SP) #0 {0}", elseLabel);
			this.gen_SubStack(1);
		}
		
		public void gen_Label(string label)
		{
			writer.WriteLine("{0}:", label);
		}
		
		public void gen_MultiplyingOp(Semantic_rec multiplyinOp_rec)
		{
			if(multiplyinOp_rec.MultiplyingOp == MultiplyingOp.Divide.GetHashCode())
				writer.WriteLine("DIVS");
			else if(multiplyinOp_rec.MultiplyingOp == MultiplyingOp.Multiply.GetHashCode())
				writer.WriteLine("MULS");
			else if(multiplyinOp_rec.MultiplyingOp == MultiplyingOp.Mod.GetHashCode())
			{
				writer.WriteLine("PUSH -2(SP)");
				writer.WriteLine("PUSH -2(SP)");
				writer.WriteLine("DIVS");
				writer.WriteLine("MULS");
				writer.WriteLine("SUBS");
			}
			else if(multiplyinOp_rec.MultiplyingOp == MultiplyingOp.And.GetHashCode())
			{
				var firstLabel = newLabel();
				var secondLabel = newLabel();
				
				writer.WriteLine("BEQ -2(SP) #0 {0}", firstLabel);
				writer.WriteLine("BEQ -1(SP) #0 {0}", firstLabel);
				this.gen_SubStack(2);
				this.gen_PushNum("1");
				this.gen_Branch(secondLabel);
				this.gen_Label(firstLabel);
				this.gen_SubStack(2);
				this.gen_PushNum("0");
				this.gen_Label(secondLabel);
			}
		}
		
		public void gen_NegSign()
		{
			writer.WriteLine("NEGS");
		}
		
		public void gen_Not()
		{
			var firstLabel = newLabel();
			var secondLabel = newLabel();
			writer.WriteLine("BNE -1(SP) #0 {0}", firstLabel);
			this.gen_SubStack(1);
			this.gen_PushNum("1");
			this.gen_Branch(secondLabel);
			
			this.gen_Label(firstLabel);
			this.gen_SubStack(1);
			this.gen_PushNum("0");
			this.gen_Label(secondLabel);
		}
		
		public void gen_PopVar(Symbol symbol)
		{
			if(symbol.Kind == SymbolKind.Parameter.GetHashCode() && symbol.ParameterMode == ParameterMode.Variable.GetHashCode())
			{
				writer.WriteLine("POP @{0}(D{1})", symbol.Offset, symbol.Level);
			}
			else
				writer.WriteLine("POP {0}(D{1})", symbol.Offset, symbol.Level);
		}
		
		public void gen_PreFunctionCall()
		{
			writer.WriteLine("ADD SP #2 SP");
			//writer.WriteLine("PUSH D{0}", level);
			//writer.WriteLine("MOV SP D{0}", level);
		}
		
		public void gen_PreProcedureCall()
		{
			writer.WriteLine("ADD SP #1 SP");
			//writer.WriteLine("PUSH D{0}", level);
			//writer.WriteLine("MOV SP D{0}", level);
		}
		
		public void gen_SetRegister(int varSize, int level)
		{
			writer.WriteLine("MOV D{0} -{1}(SP)", level, varSize + 1);
			writer.WriteLine("PUSH SP");
			this.gen_PushNum(varSize.ToString());
			writer.WriteLine("SUBS");
			writer.WriteLine("POP D{0}", level);
		}
		
		public void gen_ProcedureCallAndCleanUp(SymbolTable table, ProcedureOrFunction_rec procedure_rec)
		{
			Symbol procedure = table.GetSymbol(procedure_rec.identifier);
			if(procedure != null)
			{
				writer.WriteLine("CALL {0}", procedure.Label);
				this.gen_SubStack(procedure.Params.Count);
				writer.WriteLine("POP D{0}", procedure.Level + 1);
			}
			else
				Console.WriteLine("The procedure {0} has not been defined.", procedure_rec.identifier);
		}
		
		public void gen_ProcedureOrFunctionRet()
		{
			writer.WriteLine("RET");
		}
		
		public void gen_ProgEnd(int variableDef_rec)
		{
			writer.WriteLine("POP D0");
			writer.WriteLine("HLT");
			writer.Close();
		}
		
		public void gen_ProgStart(string mainStartLabel)
		{
			writer.WriteLine("MOV D0 0(SP)");
			writer.WriteLine("MOV SP D0");
			writer.WriteLine("ADD SP #1 SP");
			this.gen_Branch(mainStartLabel);
		}
		
		public void gen_PushNum(string number)
		{
			writer.WriteLine("PUSH #{0}", number);
		}
		
		public void gen_PushVar(Symbol symbol_rec)
		{
			if(symbol_rec.Kind == SymbolKind.Parameter.GetHashCode() && symbol_rec.ParameterMode == ParameterMode.Variable.GetHashCode())
			{
				writer.WriteLine("PUSH @{0}(D{1})", symbol_rec.Offset, symbol_rec.Level);
			}
			else
				writer.WriteLine("PUSH {0}(D{1})", symbol_rec.Offset, symbol_rec.Level);
		}
		
		public void gen_PushVariableParameter(SymbolTable table, Parameter parameter, string varName)
		{
			Symbol symbol = table.GetSymbol(varName);
			if(symbol != null)
			{
				if(symbol.Kind == SymbolKind.Parameter.GetHashCode() && symbol.ParameterMode == ParameterMode.Variable.GetHashCode())
				{
					writer.WriteLine("PUSH {0}(D{1})", symbol.Offset, symbol.Level);
				}
				else
				{
					writer.WriteLine("PUSH D{0}", symbol.Level);
					gen_PushNum(symbol.Offset.ToString());
					writer.WriteLine("ADDS");
				}
			}
			else
				Console.WriteLine("Variable {0} was not declared.", varName);
		}
		
		public void gen_ReadStatement(SymbolTable table, Ident_rec iden_rec)
		{
			Symbol tempSymbol;
			foreach(var identifier in iden_rec.identifier_List)
			{
				if((tempSymbol = table.GetSymbol(identifier)) != null)
				{
					if(tempSymbol.Kind == SymbolKind.Parameter.GetHashCode() && tempSymbol.ParameterMode == ParameterMode.Variable.GetHashCode())
						writer.WriteLine("RD @{0}(D{1})", tempSymbol.Offset, tempSymbol.Level);
					else
						writer.WriteLine("RD {0}(D{1})", tempSymbol.Offset, tempSymbol.Level);
				}
				else
				{
					Console.WriteLine("Variable {0} has not been defined.", identifier);
					return;
				}					
			}
		}
		
		public void gen_RelationalOp(Semantic_rec relationalOp_rec)
		{
			var firstLabel = newLabel();
			var secondLabel = newLabel();
			if(relationalOp_rec.RelationalOp == RelationalOp.Equal.GetHashCode())
			{
				writer.WriteLine("BEQ -2(SP) -1(SP) {0}", firstLabel);
			}
			else if(relationalOp_rec.RelationalOp == RelationalOp.LessThan.GetHashCode())
			{
				writer.WriteLine("BLT -2(SP) -1(SP) {0}", firstLabel);
			}
			else if(relationalOp_rec.RelationalOp == RelationalOp.GreaterThan.GetHashCode())
			{
				writer.WriteLine("BGT -2(SP) -1(SP) {0}", firstLabel);
			}
			else if(relationalOp_rec.RelationalOp == RelationalOp.LessThanE.GetHashCode())
			{
				writer.WriteLine("BLE -2(SP) -1(SP) {0}", firstLabel);
			}
			else if(relationalOp_rec.RelationalOp == RelationalOp.GreaterThanE.GetHashCode())
			{
				writer.WriteLine("BGE -2(SP) -1(SP) {0}", firstLabel);
			}
			else if(relationalOp_rec.RelationalOp == RelationalOp.NotEqual.GetHashCode())
			{
				writer.WriteLine("BNE -2(SP) -1(SP) {0}", firstLabel);;
			}
			this.gen_SubStack(2);
			this.gen_PushNum("0");
			this.gen_Branch(secondLabel);
			this.gen_Label(firstLabel);
			this.gen_SubStack(2);
			this.gen_PushNum("1");
			this.gen_Label(secondLabel);
		}
		
		public void gen_RepeatCheck(string repeatStartLabel)
		{
			writer.WriteLine("BEQ -1(SP) #0 {0}", repeatStartLabel);
			this.gen_SubStack(1);
		}
		
		public void gen_RepeatStart(ref string repeatStartLabel)
		{
			repeatStartLabel = newLabel();
			this.gen_PushNum("1");
			this.gen_Label(repeatStartLabel);
			this.gen_SubStack(1);
		}
		
		public void gen_SubStack(int amount)
		{
			writer.WriteLine("SUB SP #{0} SP", amount);
		}
		
		public void gen_VarDecleration(int variableDef_rec)
		{
			writer.WriteLine("ADD  SP #{0} SP", variableDef_rec);
		}
		
		public void gen_WhileEnd(string checkLabel, string finishLabel)
		{
			this.gen_Branch(checkLabel);
			this.gen_Label(finishLabel);
			this.gen_SubStack(1);
		}
		
		public void gen_WhilePostCheck(string finishLabel)
		{
			writer.WriteLine("BEQ -1(SP) #0 {0}", finishLabel);
			this.gen_SubStack(1);
		}
		
		public void gen_WhileStart(ref string checkLabel, ref string finishLabel)
		{
			checkLabel = newLabel();
			finishLabel = newLabel();
			this.gen_Label(checkLabel);
		}
		
		public void gen_WriteStatement()
		{
			writer.WriteLine("WRTS");
		}	
		
		#region private methods
		
		public string newLabel()
		{
			return "L" + (labelCounter++).ToString();
		}
		
		#endregion private methods
		
		#region properties
		
		private StreamWriter writer;
		private int labelCounter = 1;
		
		#endregion properties
	}
}

